const {
  default: makeModuleTree,
  getAllPathsToModule,
  printModuleTree
} = require("./getModulePath");
const path = require("path");

// Given a module, return it's location on disk.
function getModuleLocation(
  modules,
  mod,
  knownPaths = {},
  pathPrefix = "dist/",
  appendTrailingIndexFilesToNodeModules = false,
  entryPointModuleId = 1
) {
  let moduleHierarchy;
  let modulePaths = [];

  // Assemble a tree of modules starting at the entry point.
  let tree = makeModuleTree(modules, entryPointModuleId);
  // printModuleTree(tree);

  // If the module response contains a lookup table for modules that are required in by the current
  // module being iterated over, then calculate the hierachy of the requires to reconstruct the
  // tree.
  if (mod.lookup) {
    // Given a module, determine where it was imported within.
    console.log(`* Reconstructing require path for module ${mod.id}...`);

    let { completeEvents, incompleteEvents } = getAllPathsToModule(
      tree,
      mod.id,
      knownPaths
    );

    modulePaths = completeEvents;
  } else if (knownPaths[mod.id]) {
    // Use a known path if it exists.
    modulePaths = [[{ id: mod.id, path: knownPaths[mod.id] }]];
  } else {
    // Final fallback - the name of the file is the module id.
    console.warn(
      `* No lookup tabie for module ${
        mod.id
      }, so using identifier as require path...`
    );
    modulePaths = [[{ id: mod.id, path: `./${mod.id}` }]];
  }

  /* ['./foo'] => './foo'
   * ['../foo'] => '../foo'
   * ['uuid', './foo'] => 'node_modules/uuid/foo'
   * ['uuid', './foo', './bar'] => 'node_modules/uuid/bar'
   * ['uuid', './bar/foo', './baz'] => 'node_modules/uuid/bar/baz'
   * ['abc', './foo', 'uuid', './bar'] => 'node_modules/uuid/bar'
   */

  let rootNodeModule = "";
  let requirePaths = modulePaths.map(modulePath => {
    return modulePath.reduce((acc, mod, ct) => {
      if (!mod.path.startsWith(".")) {
        // A root node module overrides the require tree, since paths are relative to it.
        rootNodeModule = mod.path;
        return [];
      } else if (ct === modulePath.length - 1) {
        // When we get to the last item, return the filename as part of the require path.
        return [...acc, mod.path || "index"];
      } else {
        // A file import. However, this part is the directory only since further requires will
        // "stack" on top of this one. Therefore, the file that's being included is irrelevant until
        // the last item in the hierarchy (ie, the above case).
        return [...acc, path.dirname(mod.path)];
      }
    }, []);
  });

  // FIXME: currently just taking the first require path. Some smartness can be accomplished by
  // cross referencing between multiple require paths.
  let requirePath = requirePaths[0];

  if (requirePath && requirePath.length > 0) {
    modulePath = path.join(...requirePath);
  } else if (!rootNodeModule) {
    modulePath = "index";
  } else {
    // If a root node module, then leave it empty. The root node module's index is implied.
    // Ie, you don't need to do `foo/index`, you can just do `foo`.
    modulePath = appendTrailingIndexFilesToNodeModules ? "index" : "";
  }

  if (rootNodeModule) {
    modulePath = `node_modules/${rootNodeModule}/${modulePath}`;
  }

  console.log(`* ${mod.id} => ${modulePath}`);

  if (mod.id) {
    modulePath = mod.id + "";
  }
  let filePath = path.join(pathPrefix, modulePath);

  // If a filePath has a bunch of `../`s at the end, then it's broken (it broke out of the dist
  // folder!) In this cae, tell the user we need an absolute path of one of the files in order to
  // resolve it. Log out each of the paths along the require tree and it's respective module id.
  pathPrefix = pathPrefix.replace(new RegExp("/", "g"), "\\");
  if (!filePath.startsWith(pathPrefix)) {
    let err = `Don't have enough information to expand bundle into named files. The process requires the path of one of the below to be explicitly defined:`;
    // ${moduleHierarchy.map(([mod, stack]) => `- ${mod} (${stack.slice(-1)[0]})`).join('\n')}`;
    throw new Error(err);
  }

  return filePath;
}

function reverseObject(obj) {
  return Object.keys(obj).reduce((acc, i) => {
    acc[obj[i]] = i; // Reverse keys and values
    return acc;
  }, {});
}

module.exports = getModuleLocation;

if (require.main === module) {
  let modules = [
    { id: 1, code: null, lookup: { "./foo": 2, uuid: 3 } },
    { id: 2, code: null, lookup: { "./bar/baz": 4 } },
    { id: 3, code: null, lookup: { "./v1": 6, "./v4": 7 } },
    { id: 4, code: null, lookup: { uuid: 3, "../hello": 5 } },
    { id: 5, code: null, lookup: {} },

    { id: 6, code: null, lookup: { "./lib/rnd": 8 } } /* uuid/v1 */,
    { id: 7, code: null, lookup: { "./lib/rnd": 8 } } /* uuid/v4 */,
    { id: 8, code: null, lookup: {} } /* uuid/lib/rnd */
  ];

  let output = getModuleLocation(modules, modules.find(i => i.id === 8), {
    1: "./hello/world"
  });

  console.log(output);
}
